/*
Copyright (C) 2015 Marien Raat, Jochem Raat

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <guiFrontend.h>



#include <iostream>
#include <stdlib.h>             // rand(), for randomly seeding our
                                // mt19937.

#include <string>
#include <stdlib.h>            // for strtol, because stoi doesn't
                               // work in MinGw
#ifdef OS_WIN
#  include <windows.h>
#endif // OS_WIN


using namespace sf;

GUIFrontend::GUIFrontend()
{
    islandSize = Vector2u(1024, 1024);
    window = new RenderWindow(
        VideoMode(islandSize.x + 200, islandSize.y),
        "PIG: Procedural Island Generator");

#ifndef OS_WIN
    randomDevice = new std::random_device;
    mt = new std::mt19937((*randomDevice)());
    srand((*randomDevice)());
#else
    mt = new std::mt19937(time(0));
    srand(time(0));
    rand();
#endif

    mapGenerated = false;

    // Generators
    islandHeightmapGenerator = new IslandHeightmapGenerator(mt);
    markovNames = new MarkovNames("data/names.txt", 2, 12, mt);
    landMapGenerator = new LandMapGenerator;
    waterGenerator = NULL;
    windMapGenerator = new WindMapGenerator;
    heatMapGenerator = new HeatMapGenerator(mt);
    biomeGenerator = NULL;
    groundWaterMapGenerator = new GroundWaterMapGenerator(mt);
    renderedMapGenerator = new RenderedMapGenerator();

    // Images
    heightmapImage.create(islandSize.x, islandSize.y);
    riverMapImage.create(islandSize.x, islandSize.y);
    windMapImage.create(islandSize.x, islandSize.y);
    heatMapImage.create(islandSize.x, islandSize.y);
    biomeMapImage.create(islandSize.x, islandSize.y);
    groundWaterMapImage.create(islandSize.x, islandSize.y);
    renderedMapImage.create(islandSize.x, islandSize.y);

    displaySprite.setPosition(200, 0);

    if (!loadFiles()) {
        std::cout << "Some assets could not be found!\n"
                  << "Check if you are running the program "
                  << "from the main project folder."
                  << std::endl;
    }

    logoSprite.setTexture(logoTexture);
    logoSprite.setPosition(0, 0);

    // Seed interface
    float seedOffset = 202, leftPadding = 6;

    backButton = new Button
        (Vector2f(0, seedOffset), &buttonFont, "<",
         &navButtonClickedTexture, &navButtonClickedTexture,
         Vector2f(34, 36));
    forwardButton = new Button
        (Vector2f(34, seedOffset), &buttonFont, ">",
         &navButtonClickedTexture, &navButtonClickedTexture,
         Vector2f(34, 36));
    copyButton = new Button
        (Vector2f(34*2, seedOffset), &buttonFont, "Copy",
         &copyButtonTexture, &copyButtonClickedTexture,
         Vector2f(66, 36));
    pasteButton = new Button
        (Vector2f(34*2 + 66, seedOffset), &buttonFont, "Paste",
         &copyButtonTexture, &copyButtonClickedTexture,
         Vector2f(66, 36));

    seedOffset += 36;

    seedBox = new TextBox
        (Vector2f(leftPadding, seedOffset), &buttonFont,
         "Seed: ");

    // Name
    float nameOffset = seedOffset + 24;
    nameBox = new TextBox
        (Vector2f(leftPadding, nameOffset), &buttonFont,
         "Name: ");

    // Other Buttons
    float buttonOffset = nameOffset + 24;
    sf::Vector2f buttonSize = Vector2f (100, 50);
    generateButton = new Button
        (Vector2f(0, buttonOffset), &buttonFont,
         "Generate\nan island",
         &buttonTexture, &clickedButtonTexture, buttonSize);
    saveButton = new Button
        (Vector2f(0, buttonOffset + buttonSize.y), &buttonFont,
         "Save this\nisland",
         &buttonTexture, &clickedButtonTexture, buttonSize);
    heightmapButton = new Button
        (Vector2f(buttonSize.x, buttonOffset), &buttonFont,
         "Height",
         &buttonTexture, &clickedButtonTexture, buttonSize);
    riverMapButton = new Button
        (Vector2f(buttonSize.x, buttonOffset + buttonSize.y),
         &buttonFont, "Rivers",
         &buttonTexture, &clickedButtonTexture, buttonSize);
    windMapButton = new Button
        (Vector2f(buttonSize.x, buttonOffset + 2 * buttonSize.y),
         &buttonFont, "Wind",
         &buttonTexture, &clickedButtonTexture, buttonSize);
    heatMapButton = new Button
        (Vector2f(buttonSize.x, buttonOffset + 3 * buttonSize.y),
         &buttonFont, "Heat",
         &buttonTexture, &clickedButtonTexture, buttonSize);
    groundWaterMapButton = new Button
        (Vector2f(buttonSize.x, buttonOffset + 4 * buttonSize.y),
         &buttonFont, "Ground\nwater",
         &buttonTexture, &clickedButtonTexture, buttonSize);
    biomeButton = new Button
        (Vector2f(buttonSize.x, buttonOffset + 5 * buttonSize.y),
         &buttonFont, "Biomes",
         &buttonTexture, &clickedButtonTexture, buttonSize);
    renderedMapButton = new Button
        (Vector2f(buttonSize.x, buttonOffset + 6 * buttonSize.y),
         &buttonFont, "Render",
         &buttonTexture, &clickedButtonTexture, buttonSize);


    // Progress window
    progressWindow = new ProgressWindow(&buttonFont);

    currentView = RENDERED_MAP;
}

void GUIFrontend::start()
{
    draw();
    window->display();

    // Need to generate the seed on our own, for the special case of
    // having no old seed to put in the back history.
    unsigned seed = getRandomSeed();
    setSeed(seed);
    generateWithSeed(seed);

    while (window->isOpen()) {
        handleEvents();
        draw();
        window->display();
    }

    delete islandHeightmapGenerator->islandMap;
    delete islandHeightmapGenerator;
    delete markovNames;
    delete landMapGenerator->landMap;
    delete landMapGenerator->surfaceHeightmap;
    delete landMapGenerator;
    delete waterGenerator;
    delete heatMapGenerator;
    delete groundWaterMapGenerator->groundWaterMap;
    delete groundWaterMapGenerator;
    delete biomeGenerator;
    delete renderedMapGenerator;
}

bool GUIFrontend::loadFiles() {
    if (!logoTexture.loadFromFile("assets/pig_logo.png"))
        return false;
    if (!buttonFont.loadFromFile("fonts/DejaVuSerif.ttf"))
        return false;
    if (!buttonTexture.loadFromFile("assets/button.png"))
        return false;
    if (!navButtonTexture.loadFromFile("assets/navButton.png"))
        return false;
    if (!navButtonClickedTexture.loadFromFile
        ("assets/navButtonClicked.png"))
        return false;
    if (!copyButtonTexture.loadFromFile
        ("assets/copyButton.png"))
        return false;
    if (!copyButtonClickedTexture.loadFromFile
        ("assets/copyButtonClicked.png"))
        return false;
    if (!clickedButtonTexture.loadFromFile
        ("assets/buttonClicked.png"))
        return false;
    return true;
}

void GUIFrontend::handleEvents()
{
    Event event;
    while (window->pollEvent(event)) {
        if (event.type == Event::Closed) {
            window->close();
        }
        if (event.type == Event::MouseButtonPressed &&
            event.mouseButton.button == Mouse::Left) {
            Vector2f mousePos(event.mouseButton.x,
                              event.mouseButton.y);
            if (backButton->rect.contains(mousePos) &&
                !backHistory.empty()) {
                backButton->click(window);
                goBack();
            }
            if (forwardButton->rect.contains(mousePos)
                && !forwardHistory.empty()) {
                forwardButton->click(window);
                goForward();
            }
            if (copyButton->rect.contains(mousePos)) {
                copyButton->click(window);
                copySeedToClipboard(currentSeed);
            }
            if (pasteButton->rect.contains(mousePos)) {
                pasteButton->click(window);
                int seed = getSeedFromClipboard();
                if (seed >= 0)
                    generateNewWithSeed(seed);
            }
            if (generateButton->rect.contains(mousePos)) {
                generateButton->click(window);
                generateNew();
            }
            if (saveButton->rect.contains(mousePos)) {
                saveButton->click(window);
                save();
            }
            if (heightmapButton->rect.contains(mousePos)) {
                heightmapButton->click(window);
                currentView = HEIGHTMAP;
            }
            if (riverMapButton->rect.contains(mousePos)) {
                riverMapButton->click(window);
                currentView = RIVER_MAP;
            }
            if (windMapButton->rect.contains(mousePos)) {
                windMapButton->click(window);
                currentView = WIND_MAP;
            }
            if (heatMapButton->rect.contains(mousePos)) {
                heatMapButton->click(window);
                currentView = HEAT_MAP;
            }
            if (biomeButton->rect.contains(mousePos)) {
                biomeButton->click(window);
                currentView = BIOME_MAP;
            }
            if (groundWaterMapButton->rect.contains(mousePos)) {
                groundWaterMapButton->click(window);
                currentView = GROUND_WATER_MAP;
            }
            if (renderedMapButton->rect.contains(mousePos)) {
                renderedMapButton->click(window);
                currentView = RENDERED_MAP;
            }
        }
    }
}

void GUIFrontend::draw()
{
    window->clear(Color(200, 200, 200));

    if (mapGenerated) {
        if (currentView == HEIGHTMAP)
            displaySprite.setTexture(heightmapTexture);
        if (currentView == RIVER_MAP)
            displaySprite.setTexture(riverMapTexture);
        if (currentView == WIND_MAP)
            displaySprite.setTexture(windMapTexture);
        if (currentView == HEAT_MAP)
            displaySprite.setTexture(heatMapTexture);
        if (currentView == BIOME_MAP)
            displaySprite.setTexture(biomeMapTexture);
        if (currentView == GROUND_WATER_MAP)
            displaySprite.setTexture(groundWaterMapTexture);
        if (currentView == RENDERED_MAP)
            displaySprite.setTexture(renderedMapTexture);

        window->draw(displaySprite);
    }

    drawGUI();
}

void GUIFrontend::drawGUI()
{
    // Labels
    seedBox->draw(window);
    nameBox->draw(window);

    // Buttons
    backButton->draw(window);
    forwardButton->draw(window);
    copyButton->draw(window);
    pasteButton->draw(window);

    generateButton->draw(window);
    saveButton->draw(window);
    heightmapButton->draw(window);
    riverMapButton->draw(window);
    windMapButton->draw(window);
    heatMapButton->draw(window);
    groundWaterMapButton->draw(window);
    biomeButton->draw(window);
    renderedMapButton->draw(window);

    window->draw(logoSprite);
}

void GUIFrontend::drawProgress(std::string text, float peruntage)
{
    draw();
    progressWindow->draw(window, peruntage, text);
    window->display();
}

void GUIFrontend::pushBackHistory(unsigned seed)
{
    backHistory.push_back(seed);
    backButton->setNormalTexture(&navButtonTexture);
}

void GUIFrontend::pushForwardHistory(unsigned seed)
{
    forwardHistory.push_back(seed);
    forwardButton->setNormalTexture(&navButtonTexture);
}

unsigned GUIFrontend::popBackHistory()
{
    unsigned seed = backHistory.back();
    backHistory.pop_back();
    if (backHistory.empty())
        backButton->setNormalTexture(&navButtonClickedTexture);

    return seed;
}

unsigned GUIFrontend::popForwardHistory()
{
    unsigned seed = forwardHistory.back();
    forwardHistory.pop_back();
    if (forwardHistory.empty())
        forwardButton->setNormalTexture(&navButtonClickedTexture);

    return seed;
}

void GUIFrontend::clearForwardHistory()
{
    forwardHistory.clear();
    forwardButton->setNormalTexture(&navButtonClickedTexture);
}

void GUIFrontend::goBack()
{
    pushForwardHistory(currentSeed);
    setSeed(popBackHistory());
    generateWithSeed(currentSeed);
}

void GUIFrontend::goForward()
{
    pushBackHistory(currentSeed);
    setSeed(popForwardHistory());
    generateWithSeed(currentSeed);
}

unsigned GUIFrontend::getRandomSeed()
{
    return rand();
}

#ifdef OS_WIN
std::string GetClipboardText()
{
    // Try opening the clipboard
    if (! OpenClipboard(nullptr))
        return "";

    // Get handle of clipboard object for ANSI text
    HANDLE hData = GetClipboardData(CF_TEXT);
    if (hData == nullptr)
        return "";

    // Lock the handle to get the actual text pointer
    char * pszText = static_cast<char*>( GlobalLock(hData) );
    if (pszText == nullptr)
        return "";

    // Save text in a string class instance
    std::string text( pszText );

    // Release the lock
    GlobalUnlock( hData );

    // Release the clipboard
    CloseClipboard();

  return text;
}
#endif // OS_WIN

int GUIFrontend::getSeedFromClipboard()
{
#ifdef OS_WIN
    std::string seedStr = GetClipboardText();
    char *p_end;
    int seed = strtol(seedStr.c_str(), &p_end, 10);
    if (p_end > seedStr.c_str() && seed >= 0)
        return seed;
    else
        return -1;
#else
    unsigned seed;
    std::cin >> seed;
    return seed;
#endif  // OS_WIN
}

void GUIFrontend::setSeed(unsigned seed)
{
    currentSeed = seed;
    seedBox->setContent(std::to_string(seed));
    mt->seed(seed);
}

void GUIFrontend::copySeedToClipboard(unsigned seed)
{
#ifdef OS_WIN
    std::string seedStr = std::to_string(seed);

    OpenClipboard(nullptr);
    EmptyClipboard();
    HGLOBAL hg=GlobalAlloc(GMEM_FIXED,seedStr.size()+1);
    if (!hg){
        CloseClipboard();
        return;
    }
    memcpy(GlobalLock(hg),seedStr.c_str(),seedStr.size()+1);
    GlobalUnlock(hg);
    SetClipboardData(CF_TEXT,hg);
    CloseClipboard();
    GlobalFree(hg);
#else
    std::cout << "The current seed is: " << seed << std::endl;
#endif  // OS_WIN
}

void GUIFrontend::generateNew()
{
    unsigned seed = getRandomSeed();
    generateNewWithSeed(seed);
}

void GUIFrontend::generateNewWithSeed(unsigned seed)
{
    pushBackHistory(currentSeed);
    setSeed(seed);
    clearForwardHistory();

    generateWithSeed(seed);
}

void GUIFrontend::generateWithSeed(unsigned seed)
{
    // MarkovNames
    drawProgress("Generating island name...", 0.0);

    currentName = markovNames->createName();
    nameBox->setContent(currentName);

    // IslandHeightmapGenerator
    drawProgress("Generating island heightmap...", 0.05);

    int diamondSquareSize = (int) pow(2.0, ceil
                                  (log(std::max(islandSize.x,
                                                islandSize.y) - 1) /
                                   log(2.0))) + 1,
        ammountOfPasses = (int) (log(diamondSquareSize) /
                                 log(2.0));
    double *heightmapModifiers = new double[ammountOfPasses];
    double modifiers[10] = {
        0.0, // 1024
        1.3, // 512
        1.1, // 256
        0.5, // 128
        0.35, // 64
        0.23, // 32
        0.1, // 16
        //*
        0.05, // 8
        0.025, // 4
        0.003, // 2
        //*/
        /*
        0.025, // 8
        0.003, // 4
        0.0001, // 2
        //*/
    };
    // Compensation for different sizes (non-perfect)
    if (ammountOfPasses > 10) {
        for (int i = 0; i < ammountOfPasses; i++) {
            heightmapModifiers[i] =
                i < (ammountOfPasses - 10) ?
                    0.0 : modifiers[i - (ammountOfPasses - 10)];
        }
    } else {
        for (int i = 10 - ammountOfPasses; i < 10; i++)
            heightmapModifiers[i] = modifiers[i];
    }

    islandHeightmapGenerator->initializeMap(islandSize.x,
                                            islandSize.y,
                                            heightmapModifiers);
    islandHeightmapGenerator->finishMap();

    fillImage(islandHeightmapGenerator->islandMap, &heightmapImage);

    heightmapTexture.loadFromImage(heightmapImage);

    double waterLevel =
        islandHeightmapGenerator->modifiedHeightValue(0.5);

    // LandMapGenerator
    drawProgress("Generating island land map...", 0.15);

    landMapGenerator->setWaterLevel(waterLevel);
    landMapGenerator->generateLandMap(
        islandHeightmapGenerator->islandMap,
        islandSize.x, islandSize.y);

    // WaterGenerator
    drawProgress("Generating rivers...", 0.3);

    if (waterGenerator != NULL) delete waterGenerator;

    WaterSettings waterSettings;

    waterSettings.size = islandSize.x;
    waterSettings.waterLevel = waterLevel;
    waterSettings.landmap = landMapGenerator->landMap;
    waterSettings.heightmap = islandHeightmapGenerator->islandMap;

    waterGenerator = new WaterGenerator(waterSettings, mt);
    waterGenerator->initializeNewMap();
    // Generate several rivers
    for (int i = 0; i < 75; i++) {
        drawProgress("Adding rivers...", 0.3 + (0.4 - 0.3) *
                     ((float) i / 75.0f));
        waterGenerator->addRiver();
    }
    drawProgress("Completing rivers...", 0.4);
    waterGenerator->completeRivers();

    fillImage(waterGenerator->getDisplayWatermap(), &riverMapImage);

    riverMapTexture.loadFromImage(riverMapImage);


    // WindMapGenerator
    drawProgress("Generating wind map...", 0.5);

    landMapGenerator->generateSurfaceHeightmap(
        islandHeightmapGenerator->islandMap,
        islandSize.x, islandSize.y);

    double *windMap = windMapGenerator->generateWindMap(
        landMapGenerator->surfaceHeightmap,
        islandSize.x, islandSize.y);

    fillImage(windMap, &windMapImage);

    windMapTexture.loadFromImage(windMapImage);


    // HeatMapGenerator
    drawProgress("Generating heat map...", 0.6);

    double *heatMap = heatMapGenerator->generateHeatMap(
        islandHeightmapGenerator->islandMap,
        islandSize.x, islandSize.y);

    fillImage(heatMap, &heatMapImage);

    heatMapTexture.loadFromImage(heatMapImage);


    // GroundWaterMapGenerator
    drawProgress("Generating ground water map...", 0.7);

    bool *waterMap = new bool[islandSize.x * islandSize.y];
    for (int i = 0; i < islandSize.x * islandSize.y; i++)
        waterMap[i] =
            (islandHeightmapGenerator->islandMap[i] < waterLevel) ||
            (waterGenerator->getDisplayWatermap()[i] > 0.0);

    groundWaterMapGenerator->generateGroundWaterMap(
        islandSize.x, islandSize.y,
        islandHeightmapGenerator->islandMap,
        waterMap,
        heatMap);

    fillImage(groundWaterMapGenerator->groundWaterMap,
              &groundWaterMapImage);

    groundWaterMapTexture.loadFromImage(groundWaterMapImage);


    // BiomeGenerator
    drawProgress("Generating biome map...", 0.99);

    if (biomeGenerator != NULL) delete biomeGenerator;

    BiomeSettings biomeSettings;

    biomeSettings.size = islandSize.x;
    biomeSettings.heightmap = islandHeightmapGenerator->islandMap;
    biomeSettings.landmap = landMapGenerator->landMap;
    biomeSettings.groundWatermap = groundWaterMapGenerator
        ->groundWaterMap;
    biomeSettings.heatmap = heatMap;
    biomeSettings.windmap = windMap;
    biomeSettings.watermap = waterGenerator->displayWatermap;
    biomeSettings.waterLevel = waterLevel;
    biomeGenerator = new BiomeGenerator(biomeSettings);

    biomeGenerator->generateBiomes();

    biomeMapImage = biomeGenerator->getImage();

    biomeMapTexture.loadFromImage(biomeMapImage);

    // RenderedMapGenerator
    drawProgress("Generating rendered map...", 0.999); // TODO: better
                                                       // percentages

    renderedMapImage = renderedMapGenerator
        ->generateRenderedMap(islandHeightmapGenerator->islandMap,
                              landMapGenerator->surfaceHeightmap,
                              biomeGenerator->getBiomeMap(),
                              biomeMapImage);

    renderedMapTexture.loadFromImage(renderedMapImage);

    // Finalize map generation
    mapGenerated = true;

    delete windMap;
    delete heatMap;
    delete waterGenerator->displayWatermap;
    delete biomeGenerator->getBiomeMap();

    draw();
}

void GUIFrontend::save()
{
#ifdef OS_WIN
   const std::string DIRSEP = "\\";
#else
   const std::string DIRSEP = "/";
#endif

    std::string nameWithoutSpaces = currentName;
    for (int i = 0; i < currentName.length(); i++)
    {
        nameWithoutSpaces[i] = nameWithoutSpaces[i] == ' ' ? '_'
            : nameWithoutSpaces[i];
    }

    std::string directory = "saves" + DIRSEP + nameWithoutSpaces +
        "-" + std::to_string(currentSeed) + DIRSEP;

#ifdef OS_WIN
    TCHAR path[MAX_PATH];
    GetCurrentDirectory(MAX_PATH, path);
    std::cout << path << "\n";
    std::string saveDir = std::string(path) + "\\saves";
    directory = path + DIRSEP + directory;
    CreateDirectory(saveDir.c_str(), NULL);
    CreateDirectory(directory.c_str(), NULL);
#else
    system("mkdir saves/");
    std::string command = "mkdir ./" + directory;
    system(command.c_str());
#endif // OS_WIN

    heightmapImage.saveToFile(directory + "heightmap.png");
    riverMapImage.saveToFile(directory + "rivermap.png");
    windMapImage.saveToFile(directory + "windmap.png");
    heatMapImage.saveToFile(directory + "heatmap.png");
    groundWaterMapImage.saveToFile(directory + "groundwatermap.png");
    biomeMapImage.saveToFile(directory + "biomemap.png");
    biomeGenerator->getRawImage().saveToFile(directory
                                             + "raw-biomemap.png");
    renderedMapImage.saveToFile(directory + "renderedmap.png");
}

void GUIFrontend::fillImage(double *values, Image *image)
{
    for (int i = 0; i < islandSize.x * islandSize.y; i++) {
        int x = i % islandSize.x, y = i / islandSize.x;
        int value = values[i] * 255.0;
        image->setPixel(x, y, Color(value, value, value));
    }

    return;
}

// More accurate saving method
/*void GUIFrontend::fillImage(double *values, Image *image)
{
    for (int i = 0; i < islandSize.x * islandSize.y; i++) {
        int x = i % islandSize.x, y = i / islandSize.x;
        int value = values[i] * (16777215.0);
        int b = value / (256.0 * 256.0);
        int g = value / (256.0);
        int r = value % 256;
        image->setPixel(x, y, Color(r, g, b));
    }
}
*/
